package com.emeraldparser.collect;

import java.util.AbstractCollection;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.Set;

import com.google.common.collect.Lists;
import com.google.common.collect.Multisets;

public abstract class OneToOneTransformedMultiset<F, T> extends AbstractCollection<T> implements TransformedMultiset<F, T> {

	protected abstract T apply(F sourceElement);

	protected abstract F unapply(T targetElement);

	protected F unsafeUnapply(Object unsafeTargetElement) throws IllegalArgumentException {
		@SuppressWarnings("unchecked")
		T targetElement = (T) unsafeTargetElement;
		return unapply(targetElement);
	}

	@Override
	public int count(Object element) {
		F sourceElement;
		
		try {
			sourceElement = unsafeUnapply(element);
		} catch (IllegalArgumentException e) {
			return 0;
		}
		
		return sourceMultiset().count(sourceElement);
	}

	@Override
	public boolean add(T e) {
		F sourceElement = unapply(e);
		return sourceMultiset().add(sourceElement);
	}

	@Override
	public boolean addAll(Collection<? extends T> c) {
		List<F> sourceElements = Lists.newArrayListWithCapacity(c.size());
		for (T targetElement : c) {
			sourceElements.add(unapply(targetElement));
		}
		return sourceMultiset().addAll(sourceElements);
	}

	@Override
	public int add(T element, int occurrences) {
		return sourceMultiset().add(unapply(element), occurrences);
	}

	@Override
	public int remove(Object element, int occurrences) {
		// TODO Auto-generated method stub
		F sourceElement;
		
		try {
			sourceElement = unsafeUnapply(element);
		} catch (IllegalArgumentException e) {
			return 0;
		}
		
		return sourceMultiset().remove(sourceElement, occurrences);
	}

	@Override
	public int setCount(T element, int count) {
		return sourceMultiset().setCount(unapply(element), count);
	}

	@Override
	public boolean setCount(T element, int oldCount, int newCount) {
		return sourceMultiset().setCount(unapply(element), oldCount, newCount);
	}

	@Override
	public Set<T> elementSet() {
		final Set<F> sourceElementSet = sourceMultiset().elementSet();
		
		return new OneToOneTransformedSet<F, T>() {

			@Override
			public Set<F> sourceSet() {
				return sourceElementSet;
			}

			@Override
			public T apply(F sourceElement) {
				return OneToOneTransformedMultiset.this.apply(sourceElement);
			}

			@Override
			protected F unapply(T targetElement) {
				return OneToOneTransformedMultiset.this.unapply(targetElement);
			}

		};
	}

	@Override
	public Set<Entry<T>> entrySet() {
		final Set<Entry<F>> sourceEntrySet = sourceMultiset().entrySet();
		
		return new OneToOneTransformedSet<Entry<F>, Entry<T>>() {

			@Override
			public Set<Entry<F>> sourceSet() {
				return sourceEntrySet;
			}

			@Override
			public Entry<T> apply(Entry<F> sourceEntry) {
				return Multisets.immutableEntry(OneToOneTransformedMultiset.this.apply(sourceEntry.getElement()), sourceEntry.getCount());
			}

			@Override
			protected Entry<F> unapply(Entry<T> targetEntry) {
				return Multisets.immutableEntry(OneToOneTransformedMultiset.this.unapply(targetEntry.getElement()), targetEntry.getCount());
			}

		};
	}

	@Override
	public Iterator<T> iterator() {
		final Iterator<F> sourceIterator = sourceMultiset().iterator();
		
		return new Iterator<T>() {

			@Override
			public boolean hasNext() {
				return sourceIterator.hasNext();
			}

			@Override
			public T next() {
				return apply(sourceIterator.next());
			}

			@Override
			public void remove() {
				sourceIterator.remove();
			}

		};
	}

	@Override
	public int size() {
		return sourceMultiset().size();
	}

}
